package LatentTicTacToe.Data;

import java.util.LinkedList;
import java.util.Stack;

import LatentTicTacToe.misc.Cloner;

/**
 * Class describing situation ingame
 * 
 * @author Jiri Cermak
 * 
 */
public class Board extends AbstractBoard {

	final byte CROSS = 1;
	final byte CIRCLE = 2;

	public byte[][] realSituation;
	public byte[][] latentSituation;

	public Stack<Move> history;

	public LinkedList<Move> realSuccessors = new LinkedList<Move>();
	public LinkedList<Move> latentSuccessors = new LinkedList<Move>();

	private boolean valid = true;

	public Board(int width, int height, byte sign) {
		this.latentSituation = new byte[width][height];
		this.realSituation = new byte[width][height];
		history = new Stack<Move>();

		generateSuccessors(realSituation, realSuccessors);
		generateSuccessors(latentSituation, latentSuccessors);
	}

	/**
	 * Cloning constructor
	 * 
	 * @param board
	 * @param id
	 * @param sign
	 */
	@SuppressWarnings("unchecked")
	public Board(Board board, String id, byte sign) {
		this.id = id;

		this.realSituation = Cloner.clone(board.realSituation);
		this.latentSituation = Cloner.clone(board.latentSituation);

		if (board.history != null)
			this.history = (Stack<Move>) board.history.clone();
		else
			history = new Stack<Move>();

		generateSuccessors(realSituation, realSuccessors);
		generateSuccessors(latentSituation, latentSuccessors);

	}

	/**
	 * Cloning constructor
	 * 
	 * @param board
	 * @param id
	 * @param sign
	 */
	@SuppressWarnings("unchecked")
	public Board(Board board) {
		this.id = board.id;

		this.realSituation = Cloner.clone(board.realSituation);
		this.latentSituation = Cloner.clone(board.latentSituation);

		if (board.history != null)
			this.history = (Stack<Move>) board.history.clone();
		else
			history = new Stack<Move>();

		this.realSuccessors = (LinkedList<Move>) board.realSuccessors.clone();
		this.latentSuccessors = (LinkedList<Move>) board.latentSuccessors.clone();

	}

	public Board(byte[][] situation, Stack<Move> history, byte sign, String id) {

		this.latentSituation = Cloner.clone(situation);
		this.realSituation = Cloner.clone(situation);

		this.history = history;
		this.latentSituation[history.peek().x][history.peek().y] = 0;

		generateSuccessors(realSituation, realSuccessors);
		generateSuccessors(latentSituation, latentSuccessors);
	}

	/**
	 * Performs move
	 * 
	 * @param move
	 * @param sign
	 */
	@Override
	public void makeMove(Move move, byte sign) {

		if (!isValid(move)) {

			if (latentSuccessors.contains(move) && valid) {
				if (history.peek().equals(move)) {
					latentSituation[move.x][move.y] = (byte) (3 - sign);
					history.push(move);
					generateSuccessors(realSituation, realSuccessors);
					generateSuccessors(realSituation, latentSuccessors);
					this.id = id + "." + history.peek() + "." + move;
					this.valid = false;
				}
			}

			return;
		}

		if (valid == false) {
			removeInvalidMovesFromId();
			removeInvalidMoveFromHistory();
			addHistoryPeekToId();
		} else {
			addHistoryPeekToId();
		}

		valid = true;
		realSituation[move.x][move.y] = sign;

		if (!history.isEmpty()) {
			this.latentSituation[history.peek().x][history.peek().y] = (byte) (3 - sign);
		}

		history.push(move);

		this.latentSituation[history.peek().x][history.peek().y] = 0;

		if (isTerminal() != 0) {
			addHistoryPeekToId();
		}

		if (realSuccessors != null) {
			generateSuccessors(realSituation, realSuccessors);
			generateSuccessors(latentSituation, latentSuccessors);
		}

	}

	private void removeInvalidMoveFromHistory() {
		history.pop();
	}

	private void removeInvalidMovesFromId() {
		id = id.length() < 9 ? "0" : id.substring(0, id.length() - 6);
	}

	private void addHistoryPeekToId() {

		if (history.isEmpty()) {
			id = "0";
			return;
		}

		StringBuilder builder = new StringBuilder();

		builder.append(id);
		builder.append(".");
		builder.append(history.peek());

		id = builder.toString();
	}

	/**
	 * Computes number of rows, columns and diagonals where are only players
	 * stones, multiplied by number of his stones there minus same value for his
	 * enemy. Result is then normalized.
	 */
	private float evaluate(byte sign, byte[][] situation) {

		byte[][] count = new byte[4][3];
		float value = 0;

		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation[0].length; j++) {

				count[0][situation[j][i]]++;
				count[1][situation[i][j]]++;

			}

			count[2][situation[i][i]]++;
			count[3][situation[situation.length - i - 1][i]]++;

			for (int j = 0; j < 2; j++) {

				value += count[j][3 - sign] == 0 ? count[j][sign] : count[j][sign] == 0 ? -count[j][3 - sign] : 0;

				for (int k = 0; k < 3; k++) {
					count[j][k] = 0;
				}
			}
		}

		for (int j = 2; j < 4; j++) {
			value += count[j][3 - sign] == 0 ? count[j][sign] : count[j][sign] == 0 ? -count[j][3 - sign] : 0;
		}

		return value / 5;
	}

	/**
	 * return 1 - cross player won 2 - circle player won 0 - board is not
	 * terminal -1 - tie
	 */
	public byte isTerminal() {

		for (int i = 0; i < realSituation.length; i++) {
			for (int j = 0; j < realSituation[0].length; j++) {
				if (realSituation[j][i] != 0) {
					if (checkCoordinates(j, i, this.realSituation))
						return realSituation[j][i];
				}
			}
		}

		if (isFull())
			return -1;
		else
			return 0;
	}

	/**
	 * return 1 - player with given sign won -1 - player with given sign lost 0
	 * - tie 2 - board is not terminal
	 */
	public int getGainForPlayer(byte sign) {

		for (int i = 0; i < realSituation.length; i++) {
			for (int j = 0; j < realSituation[0].length; j++) {
				if (realSituation[j][i] != 0) {
					if (checkCoordinates(j, i, this.realSituation))
						return realSituation[j][i] == sign ? (byte) 1 : (byte) -1;
				}
			}
		}

		if (isFull())
			return 0;
		else
			return 2;
	}

	/**
	 * return 1 - cross player won 2 - circle player won 0 - board is not
	 * terminal -1 - tie
	 */
	public byte isTerminal(byte[][] situation) {
		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation[0].length; j++) {
				if (situation[j][i] != 0) {
					if (checkCoordinates(j, i, situation))
						return situation[j][i];
				}
			}
		}

		if (isFull())
			return -1;
		else
			return 0;
	}

	/**
	 * Controls if there are 5 signs next to each other which includes given
	 * position
	 * 
	 * @param x
	 * @param y
	 * @param i
	 * @param j
	 * @return
	 */
	private boolean checkCoordinates(int x, int y, byte[][] situation) {

		byte counter = 1;

		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {

				if (i != 0 || j != 0) {
					for (int k = 1; k <= 3; k++) {

						if (x + j * k >= 0 && x + j * k < situation.length && y + i * k >= 0 && y + i * k < situation[0].length) {
							if (situation[x + j * k][y + i * k] == situation[x][y]) {
								counter++;
							} else {
								break;
							}
						} else {
							break;
						}
					}

					for (int k = 1; k <= 3 - counter; k++) {
						if (x - j * k >= 0 && x - j * k < situation.length && y - i * k >= 0 && y - i * k < situation[0].length) {
							if (situation[x - j * k][y - i * k] == situation[x][y]) {
								counter++;
							} else {
								break;
							}
						} else {
							break;
						}
					}

					if (counter >= 3) {
						return true;
					}
					counter = 1;
				}
			}
		}

		return false;
	}

	/**
	 * Checks if given board is successor of current board
	 * 
	 * @param situation
	 * @return
	 */
	public boolean isValid(byte[][] situation) {

		int[] move = getLastMove(latentSituation, situation);

		if (move == null || realSituation[move[0]][move[1]] != 0) {
			return false;
		}

		return true;
	}

	/**
	 * Checks if given move is valid
	 * 
	 * @param situation
	 * @return
	 */
	@Override
	public boolean isValid(Move move) {

		if (realSituation[move.x][move.y] != 0) {
			return false;
		}

		return true;
	}

	/**
	 * Creates all possible situations that can follow current one
	 * 
	 * @param sign
	 */
	private void generateSuccessors(byte[][] situation, LinkedList<Move> succesors) {
		succesors.clear();

		for (byte i = 0; i < situation.length; i++) {
			for (byte j = 0; j < situation.length; j++) {
				if (situation[j][i] == 0) {
					succesors.add(new Move(j, i));
				}
			}
		}
	}

	/**
	 * Space saving variant of generateSuccesors
	 * 
	 * @param sign
	 */
	public LinkedList<Byte> generateSuccessors(byte[][] situation) {
		LinkedList<Byte> succesors = new LinkedList<Byte>();

		for (int i = 0; i < situation.length; i++) {
			for (int j = 0; j < situation.length; j++) {
				if (situation[j][i] == 0) {
					succesors.add((byte) (j * situation.length + i));
				}
			}
		}

		return succesors;
	}

	/**
	 * Compares given situations to find last move
	 * 
	 * @param oldSituation
	 * @param newSituation
	 * @return
	 */
	private int[] getLastMove(byte[][] oldSituation, byte[][] newSituation) {

		for (int i = 0; i < newSituation.length; i++) {
			for (int j = 0; j < newSituation[0].length; j++) {
				if (oldSituation[j][i] != newSituation[j][i]) {
					return new int[] { j, i };
				}
			}
		}

		return null;
	}

	/**
	 * Checks if it's possible to make another move
	 * 
	 * @return
	 */
	private boolean isFull() {
		for (int i = 0; i < realSituation.length; i++) {
			for (int j = 0; j < realSituation[0].length; j++) {
				if (realSituation[j][i] == 0) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Undoes last move
	 * 
	 * @param sign
	 */
	@Override
	public void reverseMove() {

		Move lastMove = history.pop();

		if (isTerminal() != 0) {
			this.id = this.id.substring(0, this.id.length() - 3);
		}

		if (valid) {

			if (!history.isEmpty())
				latentSituation[history.peek().x][history.peek().y] = 0;

			realSituation[lastMove.x][lastMove.y] = 0;
			this.id = this.id.length() > 3 ? this.id.substring(0, this.id.length() - 3) : "";

		} else {
			latentSituation[lastMove.x][lastMove.y] = 0;
			valid = true;
			removeInvalidMovesFromId();
		}

		generateSuccessors(realSituation, realSuccessors);
		generateSuccessors(latentSituation, latentSuccessors);

	}

	@Override
	public LinkedList<Move> getSuccessors() {
		return this.latentSuccessors;
	}

	private LinkedList<byte[][]> getPossibleSituations(byte sign) {

		LinkedList<byte[][]> possibleSituations = new LinkedList<byte[][]>();
		byte[][] tempSituation = Cloner.clone(latentSituation);

		for (int i = 0; i < latentSituation.length; i++) {
			for (int j = 0; j < latentSituation[0].length; j++) {
				if (tempSituation[j][i] == 0) {
					tempSituation[j][i] = sign;
					possibleSituations.add(Cloner.clone(tempSituation));
					tempSituation[j][i] = 0;
				}
			}
		}

		return possibleSituations;
	}

	@Override
	public float evaluate(byte sign) {

		LinkedList<byte[][]> possibleSituations = getPossibleSituations(sign);
		float value = 0;
		byte terminal = this.isTerminal();

		if (terminal != 0) {
			return sign == terminal ? 1 : terminal == -1 ? 0 : -1;
		}

		for (byte[][] situation : possibleSituations) {
			value += this.evaluate(sign, situation);
		}

		return value / possibleSituations.size();
	}

	@Override
	public byte isTerminalForMoves(Move parentMove, Move move, byte sign) {

		byte[][] tempBoard = Cloner.clone(latentSituation);

		tempBoard[parentMove.x][parentMove.y] = (byte) (3 - sign);
		tempBoard[move.x][move.y] = sign;

		return isTerminal(tempBoard);
	}

	@Override
	public byte[][] getLatentSituation() {
		return Cloner.clone(latentSituation);
	}

	@Override
	public AbstractBoard clone() {
		return new Board(this);
	}

}
